/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.tools.idea.insights.ui.insight

import com.android.testutils.waitForCondition
import com.android.tools.adtui.swing.FakeUi
import com.android.tools.idea.insights.AppInsightsProjectLevelControllerRule
import com.android.tools.idea.insights.LoadingState
import com.android.tools.idea.insights.ai.AiInsight
import com.android.tools.idea.insights.ai.codecontext.CodeContext
import com.android.tools.idea.insights.ai.codecontext.FakeCodeContextResolver
import com.android.tools.idea.insights.ai.transform.CodeTransformationDeterminer
import com.android.tools.idea.insights.ai.transform.CodeTransformationDeterminerImpl
import com.android.tools.idea.insights.ai.transform.CodeTransformationImpl
import com.android.tools.idea.testing.disposable
import com.google.common.truth.Truth.assertThat
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.RunsInEdt
import javax.swing.JButton
import kotlin.test.fail
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.mockito.Mockito.mock
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@RunsInEdt
class InsightBottomPanelTest {
  private val projectRule = ProjectRule()
  private val controllerRule = AppInsightsProjectLevelControllerRule(projectRule)

  @get:Rule
  val ruleChain: RuleChain =
    RuleChain.outerRule(EdtRule()).around(projectRule).around(controllerRule)

  private lateinit var fakeUi: FakeUi
  private val currentInsightFlow =
    MutableStateFlow<LoadingState<AiInsight?>>(LoadingState.Ready(null))

  @Before
  fun setup() {
    currentInsightFlow.update { LoadingState.Ready(null) }
  }

  @Test
  fun `suggest a fix button changes presentation based on the availability of a suggested fix`() =
    runBlocking {
      createInsightBottomPanel()
      val insight =
        AiInsight(
          rawInsight =
            """
        This is an insight.
        
        The fix should likely be in AndroidManifest.xml.
      """
              .trimIndent()
        )
      currentInsightFlow.value = LoadingState.Ready(insight)

      val button =
        fakeUi.findComponent<JButton> { it.name == "suggest_a_fix_button" }
          ?: fail("Suggest a fix button not found")
      waitForCondition(5.seconds) { button.text == "Suggest a fix" }
      assertThat(button.isEnabled).isTrue()
      assertThat(button.isBorderPainted).isTrue()

      currentInsightFlow.value = LoadingState.Ready(AiInsight("This is an insight"))
      waitForCondition(5.seconds) { button.text == "No suggested fix available." }
      assertThat(button.isBorderPainted).isFalse()
      assertThat(button.isEnabled).isFalse()
    }

  @Test
  fun `suggest a fix button is disabled when context is disabled`() = runTest {
    createInsightBottomPanel()
    val insight =
      AiInsight(
        rawInsight =
          """
        |This is an insight.
        |
        |The fix should likely be in AndroidManifest.xml.
      """
            .trimMargin()
      )
    currentInsightFlow.value = LoadingState.Ready(insight)

    val button =
      fakeUi.findComponent<JButton> { it.name == "suggest_a_fix_button" }
        ?: fail("Suggest a fix button not found")
    waitForCondition(5.seconds) { button.text == "Suggest a fix" }
    assertThat(button.isEnabled).isTrue()

    // Disable context
    controllerRule.fakeGeminiPluginApi.contextAllowed = false
    waitForCondition(2.seconds) { !button.isEnabled }
  }

  @Test
  fun `suggest a fix on context disabled does not take action`() = runTest {
    val mockDeterminer = mock<CodeTransformationDeterminer>()
    val mockCodeTransformation = mock<CodeTransformationImpl>()
    doAnswer { mockCodeTransformation }.whenever(mockDeterminer).getApplicableTransformation(any())
    createInsightBottomPanel(mockDeterminer)
    val insight =
      AiInsight(
        rawInsight =
          """
        |This is an insight.
        |
        |The fix should likely be in AndroidManifest.xml.
      """
            .trimMargin()
      )
    currentInsightFlow.value = LoadingState.Ready(insight)

    val button =
      fakeUi.findComponent<JButton> { it.name == "suggest_a_fix_button" }
        ?: fail("Suggest a fix button not found")
    waitForCondition(5.seconds) { button.text == "Suggest a fix" }
    assertThat(button.isEnabled).isTrue()

    // Disable context
    controllerRule.fakeGeminiPluginApi.contextAllowed = false

    button.doClick()
    verify(mockCodeTransformation, never()).apply()
  }

  private fun createInsightBottomPanel(
    determiner: CodeTransformationDeterminer =
      CodeTransformationDeterminerImpl(
        projectRule.project,
        FakeCodeContextResolver(listOf(CodeContext("a/b/c", "blah"))),
      )
  ) =
    InsightBottomPanel(
        controllerRule.controller,
        currentInsightFlow,
        projectRule.disposable,
        determiner,
      )
      .also { fakeUi = FakeUi(it) }
}
